/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package io;

import de.humatic.mmj.CoreMidiDevice;
import gnu.io.CommPortIdentifier;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiUnavailableException;
import net.java.games.input.Component;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
import tal.drivechain.plugin.Channel;
import tal.drivechain.plugin.Module;
import tal.drivechain.system.Globals;
import tal.drivechain.system.Globals.OS;
import tal.drivechain.variable.types.ArrayType;
import tal.drivechain.variable.types.DataType;
import tal.drivechain.variable.types.HashmapType;
import tal.drivechain.variable.Variable;

/**
 *
 * @author eisental
 */
public class ProbeIO extends Module {

    public void listFiles() { listFiles("."); }
    public void listFiles(String path) {
        List<String> files = new ArrayList<String>();

        File dir = new File(path);
        if (!dir.exists())
            throw new IllegalArgumentException(path + " does not exist.");
        if (!dir.isDirectory())
            throw new IllegalArgumentException(path + " is not a directory.");
        File[] fs = dir.listFiles();
        if (fs!=null) {
            for (File f : fs) {
                try {
                    files.add(f.getCanonicalPath());
                } catch (IOException e) {
                    throw new IllegalArgumentException("While getting canoncial path of " + f.getPath() + ": " + e.getMessage());
                }
            }
        }

        sendToOutputs(new Variable("files", new ArrayType(DataType.STRING), files, true), -1);
    }

    public void listHIDControllers() {
        List<HashMap> out = new ArrayList<HashMap>();
        Controller[] controllers = ControllerEnvironment.getDefaultEnvironment().getControllers();
        String[] ids = HIDIn.getHIDIds();
        for (int i=0; i<controllers.length; i++) {
            Controller c = controllers[i];
            HashMap h = new HashMap();
            h.put("name", c.getName());
            h.put("type", c.getType().toString().toLowerCase());
            h.put("id", ids[i]);
            out.add(h);
        }

        Variable outVar = new Variable("hid", new ArrayType(new HashmapType(DataType.STRING)), out, true);
        sendToOutputs(outVar, -1);
    }

    public void probeHIDController(int id) {
        Controller[] controllers = ControllerEnvironment.getDefaultEnvironment().getControllers();
        if (id<controllers.length) {
            List<HashMap> out = new ArrayList<HashMap>();
            Controller c = controllers[id];
            for (Component comp : c.getComponents()) {
                HashMap h = new HashMap();
                h.put("name", comp.getName().toLowerCase());
                h.put("relative", comp.isRelative());
                h.put("analog", comp.isAnalog());
                h.put("deadzone", (double)comp.getDeadZone());
                out.add(h);
            }

            sendToOutputs(new Variable("hid" + id, new ArrayType(new HashmapType(DataType.VAR)), out, true), -1);
        } else throw new IllegalArgumentException("HID controller id out of bounds: " + id);

    }

    public void listKeyCodes() {
        List<String> kodes = new ArrayList<String>();
        for (Field f : KeyEvent.class.getDeclaredFields()) {
            if (f.getName().startsWith("VK_")) {
                String name = f.getName().substring(3).toLowerCase();
                kodes.add(name);
            }
        }
        sendToOutputs(new Variable("keycodes", new ArrayType(DataType.STRING), kodes, true), -1);
    }

    public void listMidiDevices(String io) {
        boolean input;
        if (io.equals("inputs"))
            input = true;
        else if (io.equals("outputs"))
            input = false;
        else throw new IllegalArgumentException("Invalid syntax (should be: listMidiDevices{[@input/@output]}");
        List<HashMap> out = new ArrayList<HashMap>();
        if (Globals.getOS()==OS.Mac) {
            String[] ports;
            if (input)
                ports = de.humatic.mmj.MidiSystem.getInputs();
            else ports = de.humatic.mmj.MidiSystem.getOutputs();
            for (String p : ports) {
                boolean foundInfo = false;
                HashMap<String, Object> h = new HashMap<String, Object>();
                h.put("name", p);
                h.put("id", out.size()+1);
                for (CoreMidiDevice d : de.humatic.mmj.MidiSystem.getDevices()) {
                    if (!foundInfo && p.indexOf(d.getName())!=-1) {
                        h.put("description", "");
                        h.put("vendor", d.getManufacturer());
                        h.put("version", d.getModel());
                        out.add(h);
                        foundInfo = true;
                    }
                }
                if (!foundInfo) {
                    h.put("description", "");
                    h.put("vendor", "");
                    h.put("version", "");
                    out.add(h);
                }
            }
        } else {
            MidiDevice.Info minfo[] = javax.sound.midi.MidiSystem.getMidiDeviceInfo();
            int id = 0;
            for (MidiDevice.Info i : minfo) {
                try {
                    if ((input && javax.sound.midi.MidiSystem.getMidiDevice(i).getMaxTransmitters() != 0)
                            || (!input && javax.sound.midi.MidiSystem.getMidiDevice(i).getMaxReceivers()!=0)) {
                        HashMap<String, Object> h = new HashMap<String, Object>();
                        h.put("name", i.getName());
                        h.put("id", id);
                        h.put("description", i.getDescription());
                        h.put("vendor", i.getVendor());
                        h.put("version", i.getVersion());
                        out.add(h);
                    }
                } catch (MidiUnavailableException ex) {
                    throw new IllegalArgumentException("While getting midi info: " + ex.getMessage());
                }
                id++;
            }
        }
        String varName;
        if (input)
            varName = "midiInputs";
        else varName = "midiOutputs";
        sendToOutputs(new Variable(varName, new ArrayType(new HashmapType(DataType.STRING)), out, true), -1);
    }

    public void listSerialPorts() {
        Enumeration<CommPortIdentifier> e = CommPortIdentifier.getPortIdentifiers();
        List<HashMap<String, String>> val = new ArrayList<HashMap<String, String>>();
        while (e.hasMoreElements()) {
            CommPortIdentifier i = e.nextElement();
            HashMap<String, String> element = new HashMap<String, String>();
            element.put("name", i.getName());
            element.put("type", this.portTypeToString(i.getPortType()));
            val.add(element);
        }

        sendToOutputs(new Variable("serialPorts", new ArrayType(new HashmapType(DataType.STRING)), val, true), -1);
    }

    private String portTypeToString(int portType) {
        switch (portType) {
            case CommPortIdentifier.PORT_I2C: return "i2c";
            case CommPortIdentifier.PORT_PARALLEL: return "parallel";
            case CommPortIdentifier.PORT_RAW: return "raw";
            case CommPortIdentifier.PORT_RS485: return "rs485";
            case CommPortIdentifier.PORT_SERIAL: return "serial";
            default:
                throw new IllegalArgumentException("Unknown port type id: " + portType);
        }
    }

    @Override
    protected void msgReceived(Variable in, long timestamp, Channel channel) {
    }

}
